//********************************************************************************************************
// Product Name: MapWindow.dll Alpha
// Description:  The basic module for MapWindow version 6.0
//********************************************************************************************************
// The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); 
// you may not use this file except in compliance with the License. You may obtain a copy of the License at 
// http://www.mozilla.org/MPL/ 
//
// Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF 
// ANY KIND, either expressed or implied. See the License for the specificlanguage governing rights and 
// limitations under the License. 
//
// The Original Code is from MapWindow.dll version 6.0
//
// The Initial Developer of this Original Code is Ted Dunsford. Created 3/8/2008 7:19:53 AM
// 
// Contributor(s): (Open source contributors should list themselves and their modifications here). 
//
//********************************************************************************************************

using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Text;

using MapWindow;
using MapWindow.Main;
using MapWindow.Data;
using MapWindow.Drawing;
using MapWindow.Geometries;


using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;


namespace MapWindow.DirectX
{


    /// <summary>
    /// Texture
    /// </summary>
    public class TextureLayerDX: ImageLayer, ITextureLayerDX
    {
        #region Events

        /// <summary>
        /// Occurs when the texture has finished loading
        /// </summary>
        public event EventHandler TextureLoaded;

        #endregion

        #region Private Variables

        private IRasterBounds _bounds;
        private TextureStyles _style;
        private float _opacity;
        private string _filename;
        private string _worldFile;


        private TextureComponent _texture;
        private IndexCollection _indices;
        private VertexCollection _vertices;

        private long _fileSize;
      //  private bool _isInitializing;
        private System.ComponentModel.BackgroundWorker backgroundWorker1;
       // private ProgressMeter _progressMeter;
        private int _numRows;
        private int _numColumns;
        private EnvelopeDX _loadingRect;
        private bool _isLoaded;

        /// <summary>
        /// Gets whether this tile has been loaded
        /// </summary>
        public bool IsLoaded
        {
            get { return _isLoaded; }
            protected set { _isLoaded = value; }
        }

        private class DeviceFile
        {
            public string Filename;
            public Device Device;

            public DeviceFile(Device device3D, string textureFilename)
            {
                Device = device3D;
                Filename = textureFilename;
            }
        }


        #endregion

        #region Constructors

        /// <summary>
        /// Creates a new instance of Texture
        /// </summary>
        public TextureLayerDX():this(null, null)
        {
           
        }

        /// <summary>
        /// Creates a texture with the specified file.  A Load method will open the existing texture.  A Save method will overwrite it.
        /// </summary>
        /// <param name="filename">The filename to associate with this texture.</param>
        public TextureLayerDX(string filename):base(filename)
        {
            

        }

        /// <summary>
        /// Creates a texture with the specified file.  A Load method will open the existing texture.  A Save method will overwrite it.
        /// </summary>
        /// <param name="filename">The filename to associate with this texture.</param>
        /// <param name="progressHandler">An IProgressHandler for receiving status messages</param>
        public TextureLayerDX(string filename, IProgressHandler progressHandler):base(filename, progressHandler)
        {

            Configure();
        }

        /// <summary>
        /// Creates a texture with the specified file.  A Load method will open the existing texture.  A Save method will overwrite it.
        /// </summary>
        /// <param name="filename">The filename to associate with this texture.</param>
        /// <param name="container">A container to add this new layer to</param>
        /// <param name="progressHandler">An IProgressHandler for receiving status messages</param>
        public TextureLayerDX(string filename, IProgressHandler progressHandler, ICollection<ILayer> container):base(filename, progressHandler, container)
        {
            Configure();
        }

      
    

        private void Configure()
        {
            InitializeBackgoundWorker();
            Image.Bounds.BoundsChanged += new EventHandler(Bounds_BoundsChanged);
        }

        void Bounds_BoundsChanged(object sender, EventArgs e)
        {
            IsInitialized = false;
            _vertices.Invalidate();
            
        }


        private void InitializeBackgoundWorker()
        {
            backgroundWorker1 = new BackgroundWorker();
            backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
            backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
        }
    
        #endregion

        #region Methods



        /// <summary>
        /// This is the internal drawing method that follows the standard checks.  Drawing is evoked
        /// through the Draw3D command, but internally implemented in the DrawPart method.  After
        /// the initial checks, the Draw3D method will cycle the part integer from 0 to NumParts -1.
        /// </summary>
        /// <param name="args">A DrawArgsDX class with the device and camera.</param>
        protected virtual void OnDraw(DrawArgsDX args)
        {
            if (_loadingRect == null)
            {
                _loadingRect = new EnvelopeDX(Color.LightGreen, Envelope);
            }
            _loadingRect.Draw3D(args);

            // If we don't have an image loaded yet, just draw a rectangle.
            if (_isLoaded == false)
            {
                
                return;
            }
            
            _indices.BeforeDrawing(args.Device);
            _texture.BeforeDrawing(args.Device);
            _vertices.BeforeDrawing(args.Device);

            args.Device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, _vertices.Count, 0, _indices.Count / 3);

            _indices.AfterDrawing(args.Device);
            _texture.AfterDrawing(args.Device);
            _vertices.AfterDrawing(args.Device);


        }

  

        
       

        /// <summary>
        /// Clears directX memory during dispose
        /// </summary>
        public void Dispose()
        {
            if(_texture != null)_texture.Dispose();
            if(_indices != null)_indices.Dispose();
            if(_vertices != null)_vertices.Dispose();
        }

        /// <summary>
        /// Disposes the unmanaged memory and sets
        /// IsLoaded and IsInitialized to false
        /// </summary>
        public virtual void Close()
        {
            if (_texture != null) _texture.Dispose();
            if (_indices != null) _indices.Dispose();
            if (_vertices != null) _vertices.Dispose();
            _isLoaded = false;
            IsInitialized = false;
            
        }
        

       
        /// <summary>
        /// Opens the texture layer, but doesn't automatically draw it.  Rather it simply creates the texture so that it can be used
        /// by another framework.
        /// </summary>
        /// <param name="device">The Microsoft.DirectX.Direct3D.Device to draw to</param>
        /// <param name="camera"></param>
        public virtual void Open(Device device, Camera camera)
        {
            Open(device, camera, ProgressHandler);
        }

        /// <summary>
        /// This causes this textureLayer to open a data file and load the texture, but does not render the texture.
        /// This should be called before draping this texture on a different frame.
        /// </summary>
        /// <param name="device">A Microsoft.DirectX.Direct3D.Device</param>
        /// <param name="camera"></param>
        /// <param name="progressHandler">progress handler</param>
        public virtual void Open(Device device, Camera camera, IProgressHandler progressHandler)
        {
            ProgressHandler = progressHandler;
         
            LoadHeader();
            Initialize(new DrawArgsDX(device, camera));
        }

        /// <summary>
        /// Retrieves the numrows, numcolumns and georeferencing type info for this raster.
        /// This occurs during initialization automatically.
        /// </summary>
        public void LoadHeader()
        {

            string ext = System.IO.Path.GetExtension(_filename);
            if (ext == ".bmp" || ext == ".jpg" || ext == ".gif")
            {
                System.IO.FileStream fs = null;
                try
                {
                    fs = new FileStream(Filename, FileMode.Open, FileAccess.Read, FileShare.Read);
                    using (System.Drawing.Image image = System.Drawing.Image.FromStream(fs))
                    {
                        _fileSize = fs.Length;
                        _numRows = image.Height;
                        _numColumns = image.Width;
                    }
                    fs.Close();
                    fs = null;
                }
                catch
                {
                    // this is just a backup and means that the file is not a DotNet image type and I have no idea how many rows or columns it has
                    _numRows = 100;
                    _numColumns = 100;
                }
                finally
                {
                    if (fs != null)
                    {
                        try
                        {
                            fs.Close();
                        }
                        finally
                        {
                            fs.Dispose();
                        }
                    }
                }

                
            }

            if (_style == TextureStyles.Georeferenced)
            {
                if (System.IO.File.Exists(_worldFile))
                {
                    _bounds = new RasterBounds(_numRows, _numColumns, _worldFile);
                }
            }
            
        }

        /// <summary>
        /// Unlike shapefiles, textures are very easilly moved, so this method allows
        /// </summary>
        /// <param name="extents">The extents to move this layer to</param>
        public virtual void MoveTo(IEnvelope extents)
        {
            int w = 256;
            int h = 256;
            if (_texture != null)
            {
                Image size = _texture.ToImage();
                w = size.Width;
                h = size.Height;
            }
            double[] aff = new double[6];
            aff[1] = extents.Width/(double)w; // cell width
            aff[2] = 0.0; // skew X
            aff[4] = 0.0; // skew y
            aff[5] = -(extents.Height / (double)h); // cell height
            aff[0] = extents.Minimum.X; // left 
            aff[3] = extents.Maximum.Y;// top
            _bounds = new RasterBounds(h, w, aff);
          
            
        }


        /// <summary>
        /// This is necessary if the frame is supposed to move.  It will force it to be regenerated during a draw.
        /// </summary>
        public virtual void ResetMesh()
        {
            if (_vertices != null) _vertices.Invalidate();

        }


        /// <summary>
        /// this will force the texture to reload
        /// </summary>
        public virtual void ResetTexture()
        {
            if (_texture != null) _texture.Invalidate();
        }

       
        /// <summary>
        /// This layer doesn't actually support changing the image.  Instead, this simply updates the worldfile,
        /// but only if the style is georeferenced.
        /// </summary>
        public virtual void Save()
        {
            if (_style == TextureStyles.Georeferenced)
            {
                _bounds.SaveAs(_worldFile);
            }
        }

       
        #endregion

        #region Properties

        /// <summary>
        /// Describes the georeferenced boundary information for this texture, if the TextureStyle is georeferenced
        /// </summary>
        [Category("Bounds"),
         Description("Shows more detail about the geographic position of the texture layer."),
         Editor(typeof(Forms.PropertyGridEditor), typeof(System.Drawing.Design.UITypeEditor)),
         TypeConverter(typeof(Forms.GeneralTypeConverter))]
        public virtual IRasterBounds Bounds
        {
            get 
            { 
                return _bounds;
            }
            set 
            {
                _bounds = value;
                _vertices.Invalidate();
            }
        }

        


        /// <summary>
        /// Gets or sets a string filename where this texture is found on the disk.
        /// </summary>
        [Category("General"), Description("Gets or sets the string filename to use for this texture.")]
        public virtual string Filename
        {
            get { return _filename; }
            set 
            { 
                _filename = value;
                _texture = null;
                LegendText = System.IO.Path.GetFileName(_filename);
                if (_style == TextureStyles.Georeferenced)
                {
                    string ext = System.IO.Path.GetExtension(_filename);
                    switch (ext)
                    {
                        case ".bmp": _worldFile = System.IO.Path.ChangeExtension(_filename, ".bpw");
                            break;
                        case ".jpg": _worldFile = System.IO.Path.ChangeExtension(_filename, ".jgw");
                            break;
                        case ".tif": _worldFile = System.IO.Path.ChangeExtension(_filename, ".tfw");
                            break;
                        case ".png": _worldFile = System.IO.Path.ChangeExtension(_filename, ".pgw");
                            break;
                    }
                    if (System.IO.File.Exists(_worldFile) == false)
                    {
                        // try the .wld extensions which should be valid for any image format
                        string genericWorldfile = System.IO.Path.ChangeExtension(_filename, ".wld");
                        if (System.IO.File.Exists(_worldFile) == false)
                        {
                           // System.Diagnostics.Debug.WriteLine("No world file was found for " + value + " to load values.");
                           
           
                        }
                    }
                }
            }
        }

      

        /// <summary>
        /// The number of pixels in the north-south direction
        /// </summary>
        public int NumRows
        {
            get { return Image.Bounds.NumRows; }

        }

        /// <summary>
        /// Gets the number of pixels in the east-west dirction
        /// </summary>
        public int NumColumns
        {
            get { return Image.Bounds.NumColumns; }

        }

        /// <summary>
        /// Gets or sets a float from 0 to 1, where 0 is fully transparent and 1 is fully opaque
        /// </summary>
        [Category("Appearance"), Description("A float from 0 to 1, where 0 is fully transparent and 1 is fully opaque")]
        public virtual float Opacity
        {
            get { return _opacity; }
            set { _opacity = value; }
        }

        

        

        /// <summary>
        /// Describes the basic way this texture image should be applied to a given 3D structure.
        /// </summary>
        [Category("Behavior"), Description("Describes the basic way this texture image should be applied to a given 3D structure.")]
        public virtual TextureStyles Style
        {
            get { return _style; }
            set { _style = value; }
        }

        /// <summary>
        /// Gets or sets the ITexture
        /// </summary>
        public virtual Texture Texture
        {
            get { return _texture.TextureDX; }
            set 
            {
                if (_texture == null) _texture = new TextureComponent();
                _texture.TextureDX = value;
            }
        }

        /// <summary>
        /// Gets or sets a world file.  If the worldfile obides by conventional naming, this doesn't 
        /// need to be set.
        /// </summary>
        [Category("Data"), Description("Gets or sets a world file.  This will look for the conventional naming scheme when the filename is set.")]
        public string WorldFile
        {
            get { return _worldFile; }
            set { _worldFile = value; }
        }

        #endregion


        #region Private Functions

        /// <summary>
        /// Sets up the texture if it has never been drawn before
        /// </summary>
        /// <param name="args">A MapWindow.DirectX.Drawing.DrawArgsDX with a Device and Camera</param>
        public void Initialize(DrawArgsDX args)
        {
           
            LoadHeader();

            if (_indices == null) _indices = new IndexCollection(1);
            if (_vertices == null || _vertices.Count != 4 || _vertices.IsInitialized == false)
            {
                _vertices = new PositionTexturedVC();
                _vertices.DrawBox = args.Camera.DrawBox;
                // Top Left
                _vertices.AddCoordinate(Image.Bounds.TopLeft());
                _vertices.AddCoordinate(Image.Bounds.TopRight());
                _vertices.AddCoordinate(Image.Bounds.BottomLeft());
                _vertices.AddCoordinate(Image.Bounds.BottomRight());
                
            }

            if (_texture == null || _texture.IsInitialized == false)
            {

                _texture = new TextureComponent();
                //_texture.TextureDX = TextureLoader.FromFile(device, _filename, 4600, 8000, 3, Usage.AutoGenerateMipMap, Format.Unknown, Pool.Managed, Filter.Linear, Filter.Linear, Color.White.ToArgb());
                // _texture.TextureDX = TextureLoader.FromFile(device, _filename);
                backgroundWorker1.RunWorkerAsync(new DeviceFile(args.Device, Image.Filename));
                //_texture.TextureDX.SetLevelOfDetail(0);
            }
            IsInitialized = true;
            if (Inititialized != null) Inititialized(this, args);
           
        }


        

        // This event handler is where the actual,
        // potentially time-consuming work is done.
        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            // Get the BackgroundWorker that raised this event.
            BackgroundWorker worker = sender as BackgroundWorker;

            // Assign the result of the computation
            // to the Result property of the DoWorkEventArgs
            // object. This is will be available to the 
            // RunWorkerCompleted eventhandler.
            DeviceFile df = e.Argument as DeviceFile;
            e.Result = LoadTexture(df.Device, df.Filename);
        }

        // This event handler deals with the results of the
        // background operation.
        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            _texture.TextureDX = e.Result as Texture;
            //if (_progressMeter != null) _progressMeter.Reset();
            System.Diagnostics.Debug.WriteLine("Texture finished loading");
            _isLoaded = true;
            OnItemChanged();
            OnTextureLoaded(this, new EventArgs());
        }

       

        // actually loads the texture
        Texture LoadTexture(Device device, string filename)
        {
            try
            {
                return TextureLoader.FromFile(device, filename);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex);
            }
            return null;
        }


       
        

        #endregion

        #region Protected Methods

        /// <summary>
        /// Raises the Texture Loaded event
        /// </summary>
        /// <param name="sender">The object that triggered the event</param>
        /// <param name="e">A System.EventArgs parameter</param>
        protected virtual void OnTextureLoaded(object sender, EventArgs e)
        {
            if (TextureLoaded != null)
            {
                TextureLoaded(sender, e);
            }
        }

        /// <summary>
        /// Occurs immediately before drawing takes place
        /// </summary>
        /// <param name="e">A DrawVerifyArgsDX Event Argument</param>
        /// <returns>Boolean, true if drawing has been handled</returns>
        protected virtual bool OnBeforeDrawing(DrawVerifyArgsDX e)
        {
            if (_vertices != null)
            {
                if (e.Camera.DrawBox != _vertices.DrawBox)
                {
                    _vertices.Invalidate();
                }
            }
            if(BeforeDrawing != null)BeforeDrawing(this, e);
            return e.Cancel;
        }

        #endregion

        // The following code region represents methods that are common between Point, Line, and Polygon layers.
        #region Common Rendering Content

        #region Events

        /// <summary>
        /// Occurs immediately before drawing, allowing it to be cancelled
        /// </summary>
        public event EventHandler<DrawVerifyArgsDX> BeforeDrawing;

        /// <summary>
        /// Occurs after drawing, indicating if an exception or cancellation occured
        /// </summary>
        public event EventHandler<DrawCompletedArgsDX> DrawingCompleted;

        /// <summary>
        /// Occurs when the data has been loaded into the model after the first drawing method
        /// </summary>
        public event EventHandler<DrawArgsDX> Inititialized;


        #endregion

        #region Private Variables

        private int _numParts = 1;
        private int _numStages = 1;
        private DrawBox _drawBox;
        private float _previousScale;

        #endregion

        #region Methods




        /// <summary>
        /// Causes the layer or object to draw itself to the map.  This may initiate
        /// one-time loading of buffers, but should not do heavy calculations every
        /// cycle.
        /// </summary>
        /// <param name="args">A Drawing Args structure.  This is used so that this method
        /// can also be called asynchronously</param>
        public virtual void Draw3D(DrawArgsDX args)
        {
            // try
            // {
            if (IsVisible == false)
            {
                OnDrawingCompleted(new DrawCompletedArgsDX(args, false, null));
                return;
            }
            if (OnBeforeDrawing(new DrawVerifyArgsDX(args, false)) == true)
            {
                OnDrawingCompleted(new DrawCompletedArgsDX(args, true, null));
                return;
            }

            OnDrawSetup(new DrawSetupArgs(args.Camera));
            args.Camera.Position(args.Device);

            if (IsInitialized == false)
            {
                Initialize(args);
            }




            for (int stage = 0; stage < NumStages; stage++)
            {
                for (int part = 0; part < NumParts; part++)
                {
                    DrawArgsDX innerArgs = new DrawArgsDX(args.Device, args.Camera, part, stage);
                    OnDraw(innerArgs);
                }
            }
            OnDrawingCompleted(new DrawCompletedArgsDX(args, false, null));
            //}
            //catch (Exception ex)
            //{
            //    OnDrawingCompleted(this, new DrawCompletedArgsDX(args, false, ex));
            //}


        }




        /// <summary>
        /// Determines the geographic distance to use for something that is one pixel wide on the screen
        /// </summary>
        /// <param name="args">A DrawArgsDX with a Microsoft.DirectX.Device</param>
        /// <returns>A float multiplier for the scale</returns>
        public float GetScale(DrawArgsDX args)
        {
            Viewport vp = args.Device.Viewport;
            Transforms t = args.Device.Transform;
            // Find the point along the view path of the camera that intersects with the 0 elevation of the z plane
            //Vector3 pt = Plane.IntersectLine(args.Camera.XYPlane, args.Camera.CameraPosition, args.Camera.TargetPosition);
            Vector3 pt = Vector3.Subtract(args.Camera.TargetPosition, args.Camera.CameraPosition);
            float z = Math.Abs(args.Camera.CameraPosition.Z);
            pt.X = pt.X * z / pt.Z;
            pt.Y = pt.Y * z / pt.Z;
            pt = Vector3.Add(args.Camera.CameraPosition, pt);
            pt.Z = 0;

            // Project that into screen space in order to determine where the 0-elevation plane is between the near plane an the far plane of the scene
            Vector3 depth = Vector3.Project(pt, vp, t.Projection, t.View, t.World);

            // Back project two pixels from the screen into world coordinates that are separated by a distance of 1 pixel
            Vector3 center = new Vector3(vp.Width / 2, vp.Height / 2, depth.Z);
            Vector3 right = new Vector3(vp.Width / 2 + 1, vp.Height / 2, depth.Z);
            Vector3 wCenter = Vector3.Unproject(center, vp, args.Device.Transform.Projection, args.Device.Transform.View, args.Device.Transform.World);
            Vector3 wRight = Vector3.Unproject(right, vp, args.Device.Transform.Projection, args.Device.Transform.View, args.Device.Transform.World);

            // The distance between the two locations in world coordinates gives a scale value.
            Vector3 diff = Vector3.Subtract(wRight, wCenter);

            return diff.Length();
        }



        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the draw box for this PointLayer
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public DrawBox DrawBox
        {
            get { return _drawBox; }
            set { _drawBox = value; }
        }

        /// <summary>
        /// Gets the number of parts in this drawing structure
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual int NumParts
        {
            get { return _numParts; }
            protected set { _numParts = value; }
        }

        /// <summary>
        /// Gets the number of stages that will be drawn.  Each stage cycles through every part.
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual int NumStages
        {
            get { return _numStages; }
            protected set { _numStages = value; }
        }

        /// <summary>
        /// This property is computed during re-scaling.  If the ScaleMode is set to Symbolic, this
        /// will represent the scale that was used when initializing the symbols.
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual float PreviousScale
        {
            get
            {
                return _previousScale;
            }
            set
            {
                _previousScale = value;
            }
        }



        #endregion

        #region Protected Methods





        /// <summary>
        /// Fires an event when we are done with the drawing code, whether the drawing was successful, cancelled, or threw an exception.
        /// </summary>
        /// <param name="e">A DrawCompletedArgsDX parameter containing information about the drawing</param>
        protected virtual void OnDrawingCompleted(DrawCompletedArgsDX e)
        {
            if (DrawingCompleted == null) return;
            DrawingCompleted(this, e);
        }

        /// <summary>
        /// Ensures that if the mode is symbolic, the initialization code will occur every time.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnDrawSetup(DrawSetupArgs e)
        {
            if (_drawBox == null || e.Camera.DrawBox.Equals(_drawBox) == false)
            {
                IsInitialized = false;
                _drawBox = e.Camera.DrawBox;
            }
        }

        /// <summary>
        /// Invalidates this layer
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected override void OnInvalidate(object sender, EventArgs e)
        {
            _texture.Invalidate();
            _vertices.Invalidate();
           
        }






        #endregion

        #endregion





    }

}
